<?php

class Request
{
    /**
     * max loop forward
     * @var int
     */
    const MAX_LOOP_FORWARD = 4;

    /**
     * PATH_INFO
     * @var string
     */
    protected $_path = null;

    /**
     * REQUEST_URI
     * @var string;
     */
    protected $_uri = null;

    /**
     * @var string
     */
    protected $_hash = null;

    /**
     * @var string
     */
    protected $_query = null;

    /**
     * @var bool
     */
    protected $_dispatched = 0;

    /**
     * @var array
     */
    protected $_params = array();

    /**
     * get/post
     * @var string
     */
    protected $_method = 'GET';

    /**
     * @var string
     */
    protected $_controllerName = null;

    /**
     * @var string
     */
    protected $_actionName = 'index';

    /**
     * constructor
     * @return void
     */
    public function __construct($params = null)
    {
        $params && $this -> setParams($params);
    }

    /**
     * @param string $query
     * @return Request
     */
    public function setQuery($query)
    {
        $this -> _query = $query;
        return $this;
    }

    /**
     * @return string
     */
    public function getQuery()
    {
        return $this -> _query;
    }

    /**
     * @param string $string
     * @return Request
     */
    public function setHash($string)
    {
        $this -> _hash = $string;
        return $this;
    }

    /**
     * @return string
     */
    public function getHash()
    {
        return $this -> _hash;
    }

    /**
     * @return string
     */
    public function getPath()
    {
        return $this -> _path;
    }

    /**
     * Set the PATH_INFO string
     *
     * @param string $path
     * @return Request
     */
    public function setPath($path)
    {
        $this -> _path = $path;
        return $this;
    }

    /**
     * @param string $uri
     * @return Request
     */
    public function setUri($uri)
    {
        $this -> _uri = $uri;
        return $this;
    }

    /**
     * @return string
     */
    public function getUri()
    {
        return $this -> _uri;
    }

    /**
     * Retrieve a member of the $_COOKIE superglobal
     *
     * If no $key is passed, returns the entire $_COOKIE array.
     *
     * @todo How to retrieve from nested arrays
     * @param string $key
     * @param mixed $default Default value to use if key not found
     * @return mixed Returns null if key does not exist
     */
    static public function getCookie($key = null, $default = null)
    {
        return null === $key ? $_COOKIE : (isset($_COOKIE[$key]) ? $_COOKIE[$key] : $default);
    }

    /**

     * @param string $key
     * @param string $default
     * @return string
     */
    static public function getServer($key = null, $default = null)
    {
        return null === $key ? $_SERVER : (isset($_SERVER[$key])) ? $_SERVER[$key] : $default;
    }

    /**
     * @param string $key
     * @param string $default
     * @return string
     */
    static public function getEnv($key = null, $default = null)
    {
        return null === $key ? $_ENV : (isset($_ENV[$key])) ? $_ENV[$key] : $default;
    }

    /**
     * @param string $key
     * @return string
     */
    static public function getHeader($key)
    {

        // Try to get it from the $_SERVER array first
        $temp = 'HTTP_' . strtoupper(str_replace('-', '_', $key));

        if (isset($_SERVER[$temp]))
        {
            return $_SERVER[$temp];
        }

        if (function_exists('apache_request_headers'))
        {
            $headers = apache_request_headers();
            if (isset($headers[$key]))
            {
                return $headers[$key];
            }
            $header = strtolower($key);
            foreach ($headers as $k => $v)
            {
                if (strtolower($k) == $key)
                {
                    return $value;
                }
            }
        }

        return false;
    }

    /**
     * @return string
     */
    static public function getScheme()
    {
        return self::getServer('HTTPS') == 'on' ? 'https' : 'http';
    }

    /**
     * Get the HTTP host.
     *
     * "Host" ":" host [ ":" port ] ; Section 3.2.2
     * Note the HTTP Host header is not the same as the URI host.
     * It includes the port while the URI host doesn't.
     *
     * @return string
     */
    static public function getHttpHost()
    {
        $host = self::getServer('HTTP_HOST');

        if (!empty($host))
        {
            return $host;
        }

        $scheme = self::getScheme();
        $name = self::getServer('SERVER_NAME');
        $port = self::getServer('SERVER_PORT');

        if (null === $name)
        {
            return '';
        }
        elseif (($scheme == 'http' && $port == 80) || ($scheme == 'https' && $port == 443))
        {
            return $name;
        }
        else
        {
            return $name . ':' . $port;
        }
    }

    /**
     * @return string
     */
    static public function getClientIp()
    {
        if (self::getServer('HTTP_CLIENT_IP'))
        {
            self::getServer('HTTP_CLIENT_IP');
        }

        if (self::getServer('HTTP_X_FORWARDED_FOR'))
        {
            return self::getServer('HTTP_X_FORWARDED_FOR');
        }

        return self::getServer('REMOTE_ADDR', '127.0.0.1');
    }

    /**
     * Is https secure request
     *
     * @return boolean
     */
    static public function isSecure()
    {
        return (self::getScheme() === 'https');
    }

    /**
     *
     * @return boolean
     */
    static public function isFlash()
    {
        return strpos(self::getHeader('USER_AGENT'), ' flash') !== false;
    }

    /**
     * Is the request a Javascript XMLHttpRequest?
     *
     * Should work with Prototype/Script.aculo.us, possibly others.
     *
     * @return boolean
     */
    static public function isAjax()
    {
        return (self::getHeader('X_REQUESTED_WITH') == 'XMLHttpRequest');
    }

    /**
     * Was the request made by POST?
     *
     * @return boolean
     */
    public function isPost()
    {
        return 'POST' == $this -> getMethod();
    }

    /**
     * Was the request made by GET?
     *
     * @return boolean
     */
    public function isGet()
    {
        return 'GET' == $this -> getMethod();
    }

    /**
     * set is dispatched
     * @return bool
     */
    public function setDispatched($flag)
    {
        $this -> _dispatched = (bool)$flag;
        return $this;
    }

    /**
     * is dispatched
     * @return bool
     */
    public function isDispatched()
    {
        return $this -> _dispatched;
    }

    /**
     * get controller name
     * @return string
     */
    public function getControllerName()
    {
        return $this -> _controllerName;
    }

    /**
     * @param string $controllerName
     * @return Request
     */
    public function setControllerName($controllerName)
    {
        $this -> _controllerName = strtolower($controllerName);
        return $this;
    }

    /**
     * get action name
     * @return string
     */
    public function getActionName()
    {
        return $this -> _actionName;
    }

    /**
     * set action name
     * @param string $actionName
     * @return Request
     */
    public function setActionName($actionName)
    {
        $this -> _actionName = strtolower($actionName);
        return $this;
    }

    /**
     * get method name
     * @return string
     */
    public function getMethod()
    {
        return $this -> _method;
    }

    /**
     * set method name
     * @param string $method
     * @return Request
     */
    public function setMethod($method)
    {
        $this -> _method = $method;
        return $this;
    }

    /**
     * @return string
     */
    public function getActionId()
    {
        return $this -> _controllerName . ':' . $this -> _actionName;
    }

    /**
     * @param string $actionId
     * @return Request
     */
    public function setActionId($actionId)
    {
        $arr = explode(':', $actionId);
        $this -> setControllerName($arr[0]);
        $this -> setActionName(isset($arr[1]) ? $arr[1] : 'index');
        return $this;
    }

    /**
     * @param string $key
     * @param any $value
     * @return any
     */
    public function getParam($key, $value = null)
    {
        if (method_exists($this, $method = 'get' . ucfirst($key)))
        {
            return $this -> {$method}();
        }
        return isset($this -> _params[$key]) ? $this -> _params[$key] : $value;
    }

    /**
     * @param string $key
     * @param any $value
     * @return Request
     */
    public function setParam($key, $value)
    {
        if (method_exists($this, $method = 'set' . ucfirst($key)))
        {
            return $this -> {$method}($value);
        }
        else
        {
            $this -> _params[$key] = $value;
        }
        return $this;
    }

    /**
     * @return Request
     */
    public function getParams()
    {
        return $this -> _params;
    }

    /**
     * set params
     * @param array $params
     * @param Request
     */
    public function setParams($params)
    {
        if (!empty($params))
        {
            foreach ($params as $key => $value)
            {
                $this -> setParam($key, $value);
            }
        }
        return $this;

    }

    /**
     * @param string $actionId
     * @return bool
     */
    public function isDispatchable($actionId = null, $class = null)
    {
        if (null == $class)
        {
            $arr = explode(':', $actionId);
            $class = implode('_', array_map('ucfirst', explode('.', str_replace('@', '.controller.', $arr[0]))));
        }

        if (class_exists($class, 0))
        {
            return 1;

        }

        if (file_exists($file = NUO_MODULE_PATH . '/' . strtolower(str_replace('_', '/', $class)) . '.php'))
        {
            require_once $file;
            if (class_exists($class, 0))
            {
                return 1;
            }
        }

        return 0;
    }

    /**
     * dispatch
     * @return string
     */
    public function dispatch($params = null, $actionId = null)
    {
        if (null != $params)
        {
            $this -> setParams($params);
        }

        if (null != $actionId)
        {
            $this -> setActionId($actionId);
        }

        $maxLoop = self::MAX_LOOP_FORWARD;

        ob_start();

        $controller = null;

        do
        {
            /**
             * @var string
             */
            $class = implode('_', array_map('ucfirst', explode('.', str_replace('@', '.controller.', $this -> _controllerName))));

            if (!$this -> isDispatchable(null, $class))
            {
                throw new Exception(sprintf("controller %s is not dispatchable!", $this -> getActionId()));
            }

            /**
             * @var Controller
             */
            $controller = new $class($this);

            /**
             * dispatch an action.
             */
            $controller -> dispatch($this -> _actionName);

        }
        while( --$maxLoop >0 && !$this->_dispatched);

        $result = ob_get_clean();

        $format = strtolower($this -> getParam('format', 'html'));

        if ('json' == $format)
        {

            return json_encode($this -> _controller -> data);
        }

        if ('jsonp' == $format)
        {
            $callback = $this -> getParam('callback', 'callback');

            return sprintf("%s(%s);", $callback, json_encode($controller -> data));

        }
        return $controller -> render() . $result;
    }

}
